package com.liruo.treasureattic.common.util.html.bookmark.impl;

import com.liruo.treasureattic.common.util.err.ParseException;
import com.liruo.treasureattic.common.util.html.bookmark.ParseResult;
import com.liruo.treasureattic.common.util.html.bookmark.UrlImporter;
import com.liruo.treasureattic.common.util.html.bookmark.model.Folder;
import com.liruo.treasureattic.common.util.html.bookmark.model.Url;
import org.apache.commons.lang3.StringUtils;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

/**
 * @Author:liruo
 * @Date:2022-12-03-21:49:46
 * @Desc 适用于google，edge，firefox
 *
 * google, edeg, firefox===
 *
 * - body > dl 内容根元素
 *     - body > dl > dt  内容
 *         - dt  有personal_toolbar_folder="true"是浏览器工具栏上的书签目录以及书签，一般是第一个(也可能不是，比如firefox的)，其他dt就是放在其他地方的
 *         - url的dt
 *             - body > dl > dt > a，href就是网址，text就是name，可能有icon(==data:image/png;base64, BASE64)
 *         - 目录的dt
 *             - body > dl > dt > h3 text就是name
 *             - body > dl > dt >dl 是目录内容根元素
 *             - body > dl > dt >dl > dt 是目录下的内容
 * - 可以理解为：
 *     - bookmarks目录下有n个目录，personal_toolbar_folder="true"的目录是保存了浏览器工具栏上的书签目录以及书签的一个工具栏书签目录
 *     - 工具栏书签目录下才是浏览器工具栏上的书签目录以及书签
 *     - bookmarks目录除了工具栏书签目录，就是保存其他地方的书签以及书签目录
 * - 树形解析：
 *     - 传入一个根folder的name，没有就用bookmark
 *     - 拿到body > dl > dt 并遍历
 *         - 判断是目录方式：有a标签（最好），或者children  >  1
 *         - 是目录
 *             - 创建folder， 拿h3的text当name(personal_toolbar_folder="true" name=toolbar)，遍历 dl > dt
 *         - 是url
 *             - 创建url，拿a的text当name， href当url
 *
 */
public class CommonHtmlUrlImporter implements UrlImporter {
    /**
     * 是folder的content，也是判断是folder的依据
     */
    private static final String FOLDER_ASSERT = "> dl";
    /**
     * folder的元素标签
     */
    private static final String FOLDER_ELEMENT_TAG = "dt";
    /**
     *  folder的name所在tag，text就是folder的name
     */
    private static final String FOLDER_NAME_TAG = "> h3";
    /**
     *  toolbar 保存了浏览器工具栏上的书签目录以及书签的一个工具栏书签目录
     */
    private static final String FOLDER_TOOLBAR_NAME_TAG_ATTR = "personal_toolbar_folder";
    public static final String TOOLBAR_FOLDER_NAME = "toolbar";
    private static final String FOLDER_TOOLBAR_NAME_TAG_ATTR_VALUE = "true";
    /**
     * url所在的标签，text就是url的name
     */
    private static final String URL_TAG = "> a";
    /**
     * url的值
     */
    private static final String URL_VALUE = "href";
    /**
     * url的icon
     */
    private static final String URL_ICON_TAG_ATTR = "icon";

    private final String rootFolderName;
    private final boolean needIcon;
    public CommonHtmlUrlImporter(){
        this("bookmark", false);
    }
    public CommonHtmlUrlImporter(boolean needIcon){
        this("bookmark", needIcon);
    }
    public CommonHtmlUrlImporter(String rootFolderName){
        this(rootFolderName, false);
    }
    public CommonHtmlUrlImporter(String rootFolderName, boolean needIcon){
        this.rootFolderName = rootFolderName;
        this.needIcon = needIcon;
    }
    /**
     * @return ParseResult的rootFolder是一个总folder
     *          浏览器工具栏上的书签目录以及书签在一个叫toolbar的folder里,toolbar在rootFolder中
     *          其他书签就直接在rootFolder下
     */
    @Override
    public ParseResult parse(String html) {
        Document document = Jsoup.parse(html);
        ParseResult parseResult = new ParseResult();
        Folder rootFolder = doForFolder(document.body(), parseResult);
        parseResult.setRootFolder(rootFolder);
        return parseResult;
    }

    /**
     * @return ParseResult的rootFolder是一个总folder
     *          浏览器工具栏上的书签目录以及书签在一个叫toolbar的folder里
     *          其他书签就直接在rootFolder下
     */
    @Override
    public ParseResult parse(InputStream inputStream, Charset charset) {
        Document document = null;
        try {
            document = Jsoup.parse(inputStream, charset.name(), "");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        ParseResult parseResult = new ParseResult();
        Folder rootFolder = doForFolder(document.body(), parseResult);
        parseResult.setRootFolder(rootFolder);
        return parseResult;
    }

    private Folder doForFolder(Element folderElement, ParseResult parseResult){
        Folder folder = getFolder(folderElement, parseResult);
        parseResult.addFolderSize(1);

        Element content = folderElement.selectFirst(CommonHtmlUrlImporter.FOLDER_ASSERT);
        if(content == null){
            throw new ParseException("parse folder's subFolders and urls fail, when parse the "
                    +  (parseResult.getFolderSize() + 1)
                    + " folder[name=" + folder.getName() + "]"
            );
        }
        content.children()
                .forEach(child->{
                    if(!child.tagName().equalsIgnoreCase(CommonHtmlUrlImporter.FOLDER_ELEMENT_TAG)){
                        return;
                    }
                    if(isFolder(child)){
                        folder.addSubFolder(doForFolder(child, parseResult));
                    }else{
                        folder.addUrl(doForUrl(child, parseResult));
                    }
                });
        return folder;
    }

    private Folder getFolder(Element folderElement, ParseResult parseResult) {
        Folder folder;
        if(parseResult.getFolderSize() == 0){
            folder = new Folder(this.rootFolderName);
        }else{
            Element h3 = folderElement.selectFirst(CommonHtmlUrlImporter.FOLDER_NAME_TAG);
            if(h3 == null){
                throw new ParseException("parse folder name fail, when parse the " + (parseResult.getFolderSize() + 1)+ " folder");
            }
            if(isToolbar(h3)){
                folder = new Folder(CommonHtmlUrlImporter.TOOLBAR_FOLDER_NAME);
            }else{
                folder = new Folder(h3.text());
            }
        }
        return folder;
    }

    private Url doForUrl(Element urlElement, ParseResult parseResult){
        Element tagA = urlElement.selectFirst(CommonHtmlUrlImporter.URL_TAG);
        if(tagA == null){
            throw new ParseException("parse url fail, when parse the "
                    + (parseResult.getFolderSize() + 1)
                    + " folder "
                    + "and the "
                    + parseResult.getUrlSize() + 1
                    + " url"
            );
        }
        parseResult.addUrlSize(1);
        String url = tagA.attr(CommonHtmlUrlImporter.URL_VALUE);
        String urlName = tagA.text();
        if(!this.needIcon){
            return new Url(url, urlName);
        }
        return new Url(url, urlName, tagA.attr(CommonHtmlUrlImporter.URL_ICON_TAG_ATTR));
    }
    private boolean isFolder(Element element){
        return element.selectFirst(CommonHtmlUrlImporter.FOLDER_ASSERT) != null;
    }
    private boolean isToolbar(Element h3){
        String toolbarFolder = h3.attr(CommonHtmlUrlImporter.FOLDER_TOOLBAR_NAME_TAG_ATTR);
        if(StringUtils.isBlank(toolbarFolder)){
            return false;
        }
        return toolbarFolder.equalsIgnoreCase(CommonHtmlUrlImporter.FOLDER_TOOLBAR_NAME_TAG_ATTR_VALUE);
    }
}
